package wdlog

import (
	"fmt"
	"io"
	"log"
	"os"
	"runtime"
	"strconv"
	"strings"
	"time"
	"context"
)


type logLevel byte
type logOptions byte

var loglevel logLevel = 4
var logoptions = W_FileName | W_CallName | W_Line
var Filehand *os.File

var logtextpipe = make(chan string,1048756)


const (
	W_Crash   logLevel = 1 //程序崩溃
	W_Warning logLevel = 2 //一般警告
	W_Error   logLevel = 3 //执行错误
	W_Debug   logLevel = 4 //调试信息
)

//日期时间一定打印，如需修改请在本包init函数中修改log选项。
const (
	W_FileName logOptions = 1 << 0
	W_CallName logOptions = 1 << 1
	W_Line     logOptions = 1 << 2
)


var ctx context.Context
var ctxcall context.CancelFunc
//    // 控制输出日志信息的细节，不能控制输出的顺序和格式。
//     // 输出的日志在每一项后会有一个冒号分隔：例如2009/01/23 01:23:23.123123 /a/b/c/d.go:23: message
//     Ldate         = 1 << iota     // 日期：2009/01/23
//     Ltime                         // 时间：01:23:23
//     Lmicroseconds                 // 微秒级别的时间：01:23:23.123123（用于增强Ltime位）
//     Llongfile                     // 文件全路径名+行号： /a/b/c/d.go:23
//     Lshortfile                    // 文件名+行号：d.go:23（会覆盖掉Llongfile）
//     LUTC                          // 使用UTC时间
//     LstdFlags     = Ldate | Ltime // 标准logger的初始值

var std = log.New(os.Stderr, "", log.LstdFlags)

func init() {
	//设置自定义的输出前缀
	std.SetPrefix("WDLOG-->")
	std.SetFlags(log.Ldate | log.Ltime)
}
//SetLogToTerminal ..设置输出到控制台
func SetLogToTerminal(){
	if ctxcall!=nil{
		ctxcall()
	}
	ctx,ctxcall = context.WithCancel(context.Background())
	go func(ctx context.Context){
		for{
			select{
				case text:= <- logtextpipe :
				if text!=""{
					std.Printf(text)
				}
			case <-ctx.Done():
				return
			}
		}
	}(ctx)
}
//SetstdToFile 要将日志输出到那个文件
//示例：SetstdToFile("D:/std.txt")
func SetLogToFile(filepath string) {
	var err error
	Filehand, err = os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		panic("std file open fail!!!")
	}
	std.SetOutput(Filehand)
	go func(){
		select {
		case text,isClose  := <- logtextpipe:
			if !isClose {
				Filehand.Close()
			}else {
				std.Printf(text)
			}
		}
	}()
}


//SetstdToDirectory 设置输出目录
//示例：SetstdToDirectory("D:/std/")
func SetLogToDirectory(path string,maxRows int,f func()) {

	if ctxcall!=nil{
		ctxcall()
	}
	ctx,ctxcall = context.WithCancel(context.Background())

	var err error
	var nowtime = time.Now().Format("2006-01-02 15:04:05")
	nowtime = strings.Replace(nowtime,":",",",-1)
	var nowRows = 0
	Filehand, err = os.OpenFile(path+nowtime+".txt", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0766)
	if err != nil {
		fmt.Println(err.Error())
		panic("std file open fail!!!")
	}
	std.SetOutput(Filehand)
	go func(ctx context.Context){
		for{
			select {
			case text,isclose := <- logtextpipe:
				if !isclose{
					//break over
				}
				if text != ""{
					std.Printf(text)
					nowRows++
				}
				if nowRows >= maxRows{
					Filehand.Close()
					nowtime = time.Now().Format("2006-01-02 15:04:05")
					Filehand, err = os.OpenFile(path+nowtime+".txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
					if err != nil {
						panic("std file open fail!!!")
					}
					std.SetOutput(Filehand)
					nowRows = 0
				}
			case <-ctx.Done():
				return
			}
		}
	}(ctx)
}

//StopWDLog ..停止日志输出
func StopWDLog(){
	close(logtextpipe)
	for a:=0;a<10;a++{
		time.Sleep(time.Second)
		if len(logtextpipe)==0{
			break
		}
	}
}
//SetstdLevel ..设置日志等级
//用法示例：SetstdLevel(wdstd.W_Debug)
func SetLogLevel(level logLevel) {
	loglevel = level
}

//SetstdOptions ..设置打印模块
//用法示例：SetstdOptions(wdstd.W_FileName | wdstd.W_CallName | wdstd.W_Line)
func SetLogOptions(opt logOptions) {
	logoptions = opt
}

//-------------------打印调试信息函数----------------------
func Debugf(format string, v ...interface{}) {
	if loglevel < W_Debug {
		return
	}
	stdinfo := getCallerName()
	stdinfo += format
	std.Printf(stdinfo, v...)
}
func Debugln(v ...interface{}) {
	if loglevel < W_Debug {
		return
	}
	stdinfo := getCallerName()
	stdinfo += fmt.Sprintln(v...)
	logtextpipe<-stdinfo
}
//-------------------一定会打印的信息-----------------------
func Warning(v ...interface{}){
	if loglevel < W_Warning{
		return
	}
	stdinfo := getCallerName()
	stdinfo += fmt.Sprintln(v...)
	logtextpipe<-stdinfo
}
//------------------打印Error信息函数------------------------
func Error(err error) bool {
	if err == nil {
		return false
	} else if err == io.EOF {
		return true
	} else {
		stdinfo := getCallerName()
		stdinfo += fmt.Sprintln(err)
		logtextpipe<- stdinfo
	}
	return true
}
func Errorf(format string, v ...interface{}) {
	if loglevel < W_Error {
		return
	}
	stdinfo := getCallerName()
	stdinfo += format
	std.Printf(stdinfo, v...)
}
func Errorln(v ...interface{}) {
	if loglevel < W_Error {
		return
	}
	stdinfo := getCallerName()
	stdinfo += fmt.Sprintln(v...)
	logtextpipe<-stdinfo
}

//Fatalf ..打印崩溃信息直接退出
func Fatalf(format string, v ...interface{}) {
	stdinfo := getCallerName()
	stdinfo += fmt.Sprintf(format, v...)
	Filehand.Close()
	std.Fatalln(stdinfo)
}

//Fatalln ..打印崩溃信息直接退出
func Fatalln(v ...interface{}) {
	stdinfo := getCallerName()
	stdinfo += fmt.Sprintln(v...)
	Filehand.Close()
	std.Fatalln(stdinfo)
}

//FatalErr ..致命错误
func FatalErr(err error) bool {
	if err == nil {
		return false
	} else {
		stdinfo := getCallerName()
		stdinfo += fmt.Sprintln(err)
		Filehand.Close()
		std.Fatalln(stdinfo)
	}
	return true
}

//PanicErr ..致命错误
func PanicErr(err error) bool {
	if err == nil {
		return false
	} else {
		stdinfo := getCallerName()
		stdinfo += fmt.Sprintln(err)
		std.Panicln(stdinfo)
	}
	return true
}

//Panicln ..打印崩溃信息进行程序退出
func Panicln(v ...interface{}) {
	stdinfo := getCallerName()
	stdinfo += fmt.Sprintln(v...)
	std.Panicln(stdinfo)
	//Filehand.Close()
}

//Panicf ..打印崩溃信息进行程序退出
func Panicf(format string, v ...interface{}) {
	stdinfo := getCallerName()
	stdinfo += fmt.Sprintf(format, v...)
	std.Panicln(stdinfo)
	//Filehand.Close()
}

//获取该函数的爷爷函数的信息..
//格式 文件名：函数名：行号
func getCallerName() (info string) {
	pc, filename, line, ok := runtime.Caller(2)
	if !ok {
		return "wdstd getCallerName() failed!!!"
	}
	info = ""
	if (logoptions & W_FileName) != 0 {
		info += filename + ": "
	}
	if (logoptions & W_CallName) != 0 {
		info += runtime.FuncForPC(pc).Name() + ":"
	}
	if (logoptions & W_Line) != 0 {
		info += strconv.Itoa(line) + "-->"
	}
	return
}
